package model;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import model.exceptions.FormulaException;
import model.exceptions.OperatorException;

public class Operator {

    private String symbol, name, argL, argR, decomposition;
    /**
     * znacznik dla logiki temporalnej: 0 - brak specjalnych funkcji 1 -
     * operator "możliżliwe" 2 - operator "zawsze" 3 - operator "następny"
     */
    private int temporal;
    /**
     * czy do użycia dekompozycji musi być zaznaczony sign bit
     */
    private boolean signBit;
    /**
     * dozwolone znaki w nazwie operatora
     */
    public static final String availableCharacktersName = "\\w\\d ąĄćĆęĘłŁńŃóÓśŚźŹżŻ";
    /**
     * doswolone znaki w nazwie arguemntu
     */
    public static final String availableCharacktersArgs = "\\w\\d";
    /**
     * niedoswolone znaki w znaku operatora
     */
    public static final String unAvailableCharacktersSign = "\\w\\d {}(),;";

    public Operator(String _symbol, String _name, String _argL, String _argR, String _signBit, String _decomposition, String _temporal)
            throws OperatorException {
        this.initOperator(_symbol, _name, _argL, _argR, _signBit, _decomposition, _temporal);
    }

    /**
     * @param line symbol \t name \t bef \t decomposition
     * @throws NullPointerException przy niewłaściwym wymiarze
     */
    public Operator(String line) throws ArrayIndexOutOfBoundsException, OperatorException {
        try {
            String[] args = line.split("\\t");
            this.initOperator(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Operator(String): error with tabs in: " + line);
            throw e;
        }
    }

    /**
     * inicjalizuje pola w operatorze, oraz wykonuje walidacje poprawności
     * argumentów
     *
     * @param _symbol
     * @param _name
     * @param _argL
     * @param _argR
     * @param _signBit
     * @param _decomposition
     * @param _temporal
     * @throws OperatorException
     */
    protected final void initOperator(String _symbol, String _name, String _argL, String _argR, String _signBit, String _decomposition, String _temporal)
            throws OperatorException {
        symbol = _symbol.replaceAll("\\s", "");
        if (Operator.validateSymbol(symbol)) {
            throw new OperatorException("forbidden operator's sign: " + _symbol);
        }

        if (Operator.validateName(_name)) {
            throw new OperatorException("forbidden operator's name: " + _name);
        }
        this.name = _name;

        argL = _argL.replaceAll("\\s", "");
        argR = _argR.replaceAll("\\s", "");
        if (Operator.validateArgs(argL, argR)) {
            throw new OperatorException("forbidden operator's arguemnts: |" + _argL + "|" + _argR + "|");
        }

        try {
            signBit = Boolean.valueOf(_signBit);
        } catch (Exception e) {
            throw new OperatorException("forbidden signBit value: " + _signBit);
        }

        decomposition = _decomposition.replaceAll("\\s", "");
        if (Operator.validateDecompozition(this.argL, this.argR, decomposition)) {
            throw new OperatorException("forbidden operator's decomposition: " + _decomposition);
        }

        try {
            this.temporal = Integer.valueOf(_temporal);
        } catch (Exception e) {
            this.temporal = 0;
        } finally {
            if (Operator.vaildateTemporal(_temporal)) {
                throw new OperatorException("forbidden operator's temporal function: " + _temporal);
            }
        }
    }

    /**
     *
     * @param name
     * @return true - niedozwolony symbol
     */
    public static boolean validateSymbol(String symbol) {
        return symbol.matches("^[" + Operator.unAvailableCharacktersSign + "]+$");
    }

    /**
     *
     * @param name
     * @return true - niedozwolona nazwa
     */
    public static boolean validateName(String name) {
        return !name.matches("^[" + Operator.availableCharacktersName + "]+$");
    }

    /**
     *
     * @param arg
     * @return true - niedozwolony argument
     */
    public static boolean validateArgs(String argL, String argR) {
        if (argL.equals(argR)) {
            return true;
        }
        String val = "^[" + Operator.availableCharacktersArgs + "]?$";
        return !(argL.matches(val) && argR.matches(val));
    }

    /**
     *
     * @param argL
     * @param argR
     * @param decomposition
     * @return true - niedozwolona dekompozycja
     */
    public static boolean validateDecompozition(String argL, String argR, String decomposition) {
        String ope = "[^\\s\\w\\d{}(),;]";
        String todo = ope + "*((" + argL + ")|(" + argR + "))" + ope + "*;[01]";
        String child = "\\{" + todo + "(," + todo + ")*\\}";
        String match = "^" + child + "(," + child + ")*$";
        return !decomposition.matches(match);
    }

    /**
     *
     * @param arg
     * @return true - niedozwolona dekompozycja
     */
    public static boolean vaildateTemporal(String arg) {
        try {
            if (arg.equals("")) {
                return false;
            }
            int val = Integer.valueOf(arg);
            return !(0 <= val || val <= 3);
        } catch (Exception e) {
            return true;
        }
    }

    public LinkedList<LinkedList<String>> decomposite(String formula, int idx, boolean signBit)
            throws ArrayIndexOutOfBoundsException {
        LinkedList<LinkedList<String>> children = new LinkedList<LinkedList<String>>();

        String bef = formula.substring(0, idx);
        String aft = formula.substring(idx + this.symbol.length());

        // System.out.println("Operator.decomposite() bef:" + bef +
        // ", aft:" + aft);

        String[] branches = this.decomposition.split("},");
        // System.out.println("Operator.decomposite() branchesCount:" + branches.length + ", " + this.symbol);
        for (String branch : branches) {
            LinkedList<String> child = new LinkedList<String>();

            String[] leaves = branch.split(",");
            // System.out.println("Operator.decomposite() leavesCount:" + leaves.length);
            for (String leaf : leaves) {
                String[] states = leaf.replaceAll("[{}]", "").split(";");

                // System.out.println("Operator.decomposite() leaf: "+leaf+", sign:"
                // + (Formula.computeSign(states[1], signBit) ? "1" : "0") + "|"
                // + states[1] + "|"
                // + signBit);

                if (states[0].replaceAll("[\\W]", "").matches(this.argL)) {
                    child.add("(" + states[0].replaceAll(this.argL, bef) + ");"
                            + (Formula.computeSign(states[1], signBit) ? "1" : "0") + ";" + this.temporal);
                    continue;
                }
                if (states[0].replaceAll("[\\W]", "").matches(this.argR)) {
                    child.add("(" + states[0].replaceAll(this.argR, aft) + ");"
                            + (Formula.computeSign(states[1], signBit) ? "1" : "0") + ";" + this.temporal);
                    continue;
                }
            }

            children.add(child);
        }

        // TODO: sortowanie malejąco po długości stringa

        return children;
    }

    @Override
    public String toString() {
        return this.symbol + " - " + this.name + ": " + this.argL + this.symbol + this.argR + " -> "
                + this.decomposition + " " + this.temporal;
    }

    /**
     * wczytuje operatory zapisane do pliku dopóki nie wystąpi IOException
     *
     * @param file stream czytający plik
     * @return lista operatorów wczytana z pliku
     * @throws FileNotFoundException
     * @throws UnsupportedEncodingException
     * @throws OperatorException
     */
    public static LinkedList<Operator> loadOperators(InputStreamReader file) throws FileNotFoundException, UnsupportedEncodingException, OperatorException {
        LinkedList<Operator> list = new LinkedList<Operator>();
        BufferedReader in = new BufferedReader(file);
        String buf;

        try {
            do {
                try {
                    buf = in.readLine();
                    if (buf.contains("[symbol]") == false) {
                        list.add(new Operator(buf));
                    }
                    // od drugiej linijki

                } catch (IOException ioe) {
                    buf = null;
                }
            } while (buf != null);
        } catch (NullPointerException npe) {
        }

        Operator.validateList(list);
        return list;
    }

    /**
     * zapisuje liste operatorów do pliku
     *
     * @param operators
     * @param filename
     */
    public static void saveOperators(LinkedList<Operator> operators, String filename) throws OperatorException {
        Operator.validateList(operators);

        String text = "[symbol]\t[nazwa]\t[lewy arguemnt]\t[prawy arguemnt]\t[dekompozycja, {} - dziecko,po ; signBit podformu\u0142y/litera\u0142u]";
        String op;
        Operator on;
        for (int i = 0; i < operators.size(); i++) {
            on = operators.get(i);
            op = on.getSymbol() + "\t" + on.getName() + "\t" + on.getArgL() + "\t" + on.getArgR() + "\t" + on.getSignBit() + "\t" + on.getDecomposition() + "\t" + on.getTemporal();
            text = text + "\n" + op;
        }
        FileWriter file = null;
        BufferedWriter buffer = null;
        PrintWriter out = null;
        try {
            file = new FileWriter(filename);
            buffer = new BufferedWriter(file);
            out = new PrintWriter(buffer);
            out.print(text);

        } catch (IOException e) {
            e.printStackTrace();

        } finally {
            if (out != null) {
                out.close();
            }
            if (buffer != null) {
                try {
                    buffer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (file != null) {
                try {
                    file.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * loguje operatory
     * @param operators
     * @return 
     */
    public static String logOperators(LinkedList<Operator> operators) {
        String text="";
        String op;
        Operator on;
        for (int i = 0; i < operators.size(); i++) {
            on = operators.get(i);
            op = on.toString();
            text = text + "\n" + op;
        }
      return text;
    }

    public String getSymbol() {
        return this.symbol;
    }

    public String getName() {
        return name;
    }

    public String getArgR() {
        return this.argR;
    }

    public String getArgL() {
        return this.argL;
    }

    public boolean getSignBit() {
        return this.signBit;
    }

    public String getDecomposition() {
        return this.decomposition;
    }

    public int getTemporal() {
        return this.temporal;
    }

    public static String getOperatorsLForRegex(LinkedList<Operator> operators) {
        String toRegex = "";
        for (Operator ope : operators) {
            if (!ope.getArgL().equals("") && ope.getArgR().equals("")) {
                toRegex += "|(" + Pattern.quote(ope.getSymbol()) + ")";
            }
        }
        return toRegex.replaceFirst("^\\|", "");
    }

    public static String getOperatorsRForRegex(LinkedList<Operator> operators) {
        String toRegex = "";
        for (Operator ope : operators) {
            if (ope.getArgL().equals("") && !ope.getArgR().equals("")) {
                toRegex += "|(" + Pattern.quote(ope.getSymbol()) + ")";
            }
        }
        return toRegex.replaceFirst("^\\|", "");
    }

    public static String getOperatorsBForRegex(LinkedList<Operator> operators) {
        String toRegex = "";
        for (Operator ope : operators) {
            if (!ope.getArgL().equals("") && !ope.getArgR().equals("")) {
                toRegex += "|(" + Pattern.quote(ope.getSymbol()) + ")";
            }
        }
        return toRegex.replaceFirst("^\\|", "");
    }

    public String createRegexMatch(LinkedList<Operator> operators) {
        String opeL = Operator.getOperatorsLForRegex(operators);
        String opeB = Operator.getOperatorsBForRegex(operators);
        String opeR = Operator.getOperatorsRForRegex(operators);

        String lit = Operator.availableCharacktersName;

        /**
         * przed operatoren \n operator \n po operatorze
         */
        String regexMatch = null;

        if (!this.argL.equals("") && this.argR.equals("")) {
            //operatorf Left
            regexMatch = "(([" + lit + "])|(" + opeL + ")|(\\)))"
                    + "(" + Pattern.quote(this.symbol) + ")"
                    + "((" + opeL + ")|(" + opeB + ")|(\\))|($))";
        } else if (!this.argL.equals("") && !this.argR.equals("")) {
            //opeerator Both
            regexMatch = "(([" + lit + "])|(" + opeL + ")|(\\)))"
                    + "(" + Pattern.quote(this.symbol) + ")"
                    + "(([" + lit + "]+)|(" + opeR + ")|(\\())";
        } else if (this.argL.equals("") && !this.argR.equals("")) {
            //operator Right
            regexMatch = "((" + opeB + ")|(" + opeR + ")|(\\()|(^))"
                    + "(" + Pattern.quote(this.symbol) + ")"
                    + "(([" + lit + "])|(" + opeR + ")|(\\())";
        }

        return regexMatch;
    }

    /**
     * sprawdza czy każdy operator ma unikalny znak
     *
     * @param operators
     * @return
     */
    static public boolean validateList(LinkedList<Operator> operators) throws OperatorException {
        for (Operator ope : operators) {
            for (Operator check : operators) {
                if (ope == check) {
                    continue;
                }
                if (ope.getSymbol().equals(check.getSymbol())) {
                    if (ope.getSignBit() == check.getSignBit()) {
                        throw new OperatorException("operators " + ope.getName() + " and " + check.getName() + " share mutual Symbol: " + ope.getSymbol());
                    }
                    continue;

                }
                if (ope.getSymbol().contains(check.getSymbol())) {
                    if (!((ope.getArgL().equals("") && check.getArgL().equals("")) || (ope.getArgR().equals("") && check.getArgR().equals("")))) {
                        throw new OperatorException("operators " + ope.getName() + " and " + check.getName() + " have including itselves Symbols: " + ope.getSymbol() + " and " + check.getSymbol());
                    }
                }
            }
        }
        return true;
    }

    /**
     * sprawdza czy operatory są poprawnie osadzone/użyte
     *
     * @param formula
     * @param operators
     * @return
     */
    static public boolean validate(String formula, LinkedList<Operator> operators) throws FormulaException {
        //walidacja poprawnego użycia operatorów
        int idx, idxPrev, start, end;
        String sign, match;
        Matcher matcher;

        for (Operator ope : operators) {
            idx = idxPrev = 0;
            sign = ope.getSymbol();
            match = ope.createRegexMatch(operators);
            //System.out.println(ope +"   "+match);
            matcher = Pattern.compile(match).matcher(formula);
            while ((idx = formula.indexOf(sign, idx)) != -1) {
                idxPrev = idxPrev - idx == 0 && idxPrev != 0 ? idxPrev - 1 : idxPrev;
                if (matcher.find(idxPrev)) {
                    start = matcher.start();
                    end = matcher.end();
                    if ((/*opeL*/(idx <= start || end < idx) && (!ope.getArgL().equals("") && ope.getArgR().equals("")))
                            || (/*opeB*/(idx <= start || end <= idx) && (!ope.getArgL().equals("") && !ope.getArgR().equals("")))
                            || (/*opeR*/(idx < start || end <= idx) && (ope.getArgL().equals("") && !ope.getArgR().equals("")))) {
                        throw new FormulaException("incorrect usage of operator " + ope.getSymbol() + " at index " + idx + ", usage: {" + formula.substring(start, end) + "} from " + start + " to " + end);
                    }
                    //System.out.println("idx " + idx + " idxPrev " + idxPrev + " start " + start + " end " + end);
                    idxPrev = ++idx;

                } else {
                    throw new FormulaException("incorrect usage of operator " + ope.getSymbol() + " at index " + idx + " - too few arguments");
                }
            }
        }
        return true;

    }
}
